package library;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Objects;

public class Loan {
    private final LoanId id;
    private final Book book;
    private final Member member;
    private final LocalDateTime borrowDate;
    private LocalDateTime dueDate;
    private LocalDateTime returnDate;
    private boolean renewed;
    
    private static final int LOAN_PERIOD_DAYS = 30;

    public Loan(LoanId id, Book book, Member member, LocalDateTime borrowDate) {
        this.id = Objects.requireNonNull(id, "Loan id cannot be null");
        this.book = Objects.requireNonNull(book, "Book cannot be null");
        this.member = Objects.requireNonNull(member, "Member cannot be null");
        this.borrowDate = Objects.requireNonNull(borrowDate, "Borrow date cannot be null");
        this.dueDate = borrowDate.plusDays(LOAN_PERIOD_DAYS);
        this.renewed = false;
    }

    public void renew(LocalDateTime renewalDate) {
        if (isReturned()) {
            throw new IllegalStateException("Cannot renew returned loan");
        }
        if (isOverdue()) {
            throw new IllegalStateException("Cannot renew overdue loan");
        }
        if (renewed) {
            throw new IllegalStateException("Loan has already been renewed once");
        }

        this.dueDate = renewalDate.plusDays(LOAN_PERIOD_DAYS);
        this.renewed = true;
    }

    public void returnBook(LocalDateTime returnDate) {
        if (isReturned()) {
            throw new IllegalStateException("Book already returned");
        }
        this.returnDate = Objects.requireNonNull(returnDate, "Return date cannot be null");
    }

    public boolean isOverdue() {
        LocalDateTime now = LocalDateTime.now();
        return !isReturned() && now.isAfter(dueDate);
    }

    public boolean isReturned() {
        return returnDate != null;
    }

    public long getDaysOverdue() {
        if (!isOverdue()) {
            return 0;
        }
        return ChronoUnit.DAYS.between(dueDate, LocalDateTime.now());
    }

    public LoanId getId() {
        return id;
    }

    public Book getBook() {
        return book;
    }

    public Member getMember() {
        return member;
    }

    public LocalDateTime getBorrowDate() {
        return borrowDate;
    }

    public LocalDateTime getDueDate() {
        return dueDate;
    }

    public LocalDateTime getReturnDate() {
        return returnDate;
    }

    public boolean isRenewed() {
        return renewed;
    }
}

// Value Object
class LoanId {
    private final String value;

    public LoanId(String value) {
        this.value = Objects.requireNonNull(value, "LoanId value cannot be null");
    }

    public String getValue() {
        return value;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        LoanId loanId = (LoanId) o;
        return Objects.equals(value, loanId.value);
    }

    @Override
    public int hashCode() {
        return Objects.hash(value);
    }
}
